home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / program / synctree.zip / SYNCTREE.C < prev    next >
C/C++ Source or Header  |  1997-03-13  |  15KB  |  600 lines

  1. /* Copyright (c) 1997, Cerious Software Inc. 
  2. **
  3. **  This source code may be used and modified freely for personal use.
  4. **  Commercial use requires permission from Cerious Software, Inc.
  5. **
  6. **    Cerious Software, Inc.
  7. **    1515 Mockingbird Ln. Suite 910
  8. **    Charlotte, NC 28209 USA
  9. **    Tel: 704-529-0200
  10. **    Fax: 704-529-0497
  11. **    E-mail: pcrews@cerious.com
  12. **    CompuServe: 71501,2470
  13. */
  14.  
  15. #include <windows.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <stdarg.h>
  19.  
  20. /* Synctree
  21. **
  22. **    Synctree [-tn] [-q] cmdfile
  23. **
  24. ** This program synchronizes the files in two directory trees
  25. ** using the information provided in a separate file (the 'cmdfile').
  26. **
  27. ** -tn Sets trace level to n (0-9)
  28. ** -q  Set test mode (no actual copies occur)
  29. **
  30. ** cmdfile structure:
  31. **
  32. **  Dir1: <First base directory>
  33. **  Dir2: <Second base directory>
  34. **  Include: <File mask(s) to include, separated by semicolons>
  35. **  Exclude: <File mask(s) to exclude, separated by semicolons>
  36. **  SkipDir: <Subdirectories to exclude>
  37. **
  38. ** A sample cmdfile would be:
  39. **
  40. **  Dir1: C:\Projects
  41. **  Dir2: \\REMOTE\C\Projects
  42. **  Include: *
  43. **  Exclude: *.obj;*.sbr;*.aps;*.res;*.tdb;*.dll;*.exe;*.bsc;*.pch
  44. **  SkipDir: Release;Debug;Help;Manual;*.old
  45. */
  46.  
  47. #define UPCHAR(c) ((int)AnsiUpper((LPSTR)c))
  48. #define LOCHAR(c) ((int)AnsiLower((LPSTR)c))
  49. #define ISDOTDIR(f) ((f)[0]=='.' && ((f)[1]==0 || (f)[1] =='.'))
  50. #define SLASH '\\'
  51.  
  52. #define ERR_MEMORY        1
  53. #define ERR_OPENCMD        2
  54. #define ERR_NOTHING        3
  55. #define ERR_NODIR        4
  56. #define ERR_USAGE        5
  57. #define ERR_NOCMD        6
  58. #define ERR_CANTCOPY    7
  59. #define ERR_DIRFILE        8
  60. #define ERR_DIRACCESS    9
  61. #define ERR_PATHSPEC    10
  62. #define ERR_MAKEDIR        11
  63.  
  64. static LPCTSTR error_msg[] = {
  65.     "Success",
  66.     "Insufficient memory for buffers (last requested = %d bytes)",
  67.     "Unable to open command file: %s",
  68.     "Neither path has any files to process (%s, %s)",
  69.     "Both paths must be specified",
  70.     "Usage: synctree [-t<n>] [-q] cmdfile",
  71.     "Command file not specified",
  72.     "Error copying %s to %s: %s",
  73.     "Cannot create directory %s; file with same name exists",
  74.     "Cannot access directory %s",
  75.     "An invalid path was specified (%s)",
  76.     "Cannot create directory %s"
  77. };
  78.  
  79. /* The file entry table contains the following information. The
  80. ** date/time is maintained in DOS format for comparisons, as comparing
  81. ** a time from a Dos or Windows 95 file system and an NTFS file system
  82. ** otherwise results in "false positives" for changes.
  83. */
  84.  
  85. typedef struct tagFINFO {
  86.     LPTSTR        name;        /* File name */
  87.     DWORD        written;    /* Date/time written (DOS format <Gag>) */
  88. } FINFO;
  89.  
  90. /* The following information is maintained for each directory tree
  91. */
  92.  
  93. typedef struct tagTREE {
  94.     int          count;
  95.     int          len;
  96.     LPTSTR      buf;
  97.     FINFO     *finfo;
  98.     LPTSTR     *includes;
  99.     LPTSTR     *excludes;
  100.     LPTSTR     *skipdirs;
  101.     TCHAR      dir[_MAX_PATH];
  102. } TREE;
  103.  
  104. int trace_level = 1, trace = 0;
  105. TREE tree1, tree2;
  106. TCHAR cmdfile[_MAX_PATH];
  107. TCHAR directory1[_MAX_PATH];
  108. TCHAR directory2[_MAX_PATH];
  109. TCHAR includes[_MAX_PATH];
  110. TCHAR excludes[_MAX_PATH];
  111. TCHAR skipdirs[_MAX_PATH];
  112. BOOL testmode;
  113. TCHAR highname[_MAX_PATH];
  114. int newcopied[3], updated[3], skipped;
  115.  
  116. /* Trace() - Output a trace message
  117. */
  118.  
  119. static void Trace(int level, LPCTSTR msg, ...)
  120. {
  121.     va_list ap;
  122.     TCHAR out[1000];
  123.  
  124.     if (level <= trace)
  125.     {
  126.         va_start(ap, msg);
  127.         if (level)
  128.             memset(out, ' ', level);
  129.         vsprintf(out + level, msg, ap);
  130.         fputs(out, stdout);
  131.         fputc('\n', stdout);
  132.     }
  133. }
  134.  
  135. /* Abort() - Output an error message & exit
  136. */
  137.  
  138. static void Abort(int err, ...)
  139. {
  140.     va_list ap;
  141.  
  142.     va_start(ap, err);
  143.     vfprintf(stderr, error_msg[err], ap);
  144.     fputc('\n', stderr);
  145.     exit(err);
  146. }
  147.  
  148. /* Warning() - Output a warning message
  149. */
  150.  
  151. static BOOL Warning(int err, ...)
  152. {
  153.     va_list ap;
  154.  
  155.     va_start(ap, err);
  156.     vfprintf(stderr, error_msg[err], ap);
  157.     fputc('\n', stderr);
  158.     return FALSE;
  159. }
  160.  
  161. /* Realloc() - realloc() with an abort if memory isn't available
  162. */
  163.  
  164. static LPVOID Realloc(LPVOID pv, int newsize)
  165. {
  166.     if ( (pv = realloc(pv, newsize)) == NULL)
  167.         Abort(ERR_MEMORY, newsize);
  168.     return pv;
  169. }
  170.  
  171. /* Malloc() - malloc() with an abort if memory isn't available
  172. */
  173.  
  174. static LPVOID Malloc(int newsize)
  175. {
  176.     LPVOID pv;
  177.  
  178.     if ( (pv = malloc(newsize)) == NULL)
  179.         Abort(ERR_MEMORY, newsize);
  180.     return pv;
  181. }
  182.  
  183. /* Calloc() - calloc() with an abort if memory isn't available
  184. */
  185.  
  186. static LPVOID Calloc(int cnt, int size)
  187. {
  188.     LPVOID pv;
  189.  
  190.     if ( (pv = calloc(cnt, size)) == NULL)
  191.         Abort(ERR_MEMORY, cnt*size);
  192.     return pv;
  193. }
  194.  
  195. /* DirName() - Extract the directory name from a file specification
  196. */
  197.  
  198. static LPTSTR DirName(LPCTSTR fullspec, LPTSTR buf)
  199. {
  200.     LPTSTR t;
  201.  
  202.     strcpy(buf, fullspec);
  203.     if ( (t = strrchr(buf, SLASH)) != NULL)
  204.         *t = 0;
  205.     else if ( (t = strrchr(buf, ':')) != NULL)
  206.         t[1] = 0;
  207.     return buf;
  208. }
  209.  
  210. /* FullSpec() - Generate a full path with directory & filename
  211. */
  212.  
  213. static LPTSTR FullSpec(LPTSTR out, LPCTSTR dirname, LPCTSTR filename)
  214. {
  215.     if (filename[0] == SLASH)
  216.         sprintf(out, "%s%s", dirname, filename);
  217.     else
  218.         sprintf(out, "%s%c%s", dirname, SLASH, filename);
  219.     return out;
  220. }
  221.  
  222. /* AddToList() - Add a file entry to the list of files
  223. **
  224. ** The list of files is initially built as a single long character array;
  225. ** thus, realloc'ing it is most likely to succeed without having to
  226. ** move the block.
  227. */
  228.  
  229. static void AddToList(TREE *ptree, LPCTSTR name, FILETIME filetime)
  230. {
  231.     WORD dosdate, dostime;
  232.     DWORD written;
  233.     int newlen = ptree->len + strlen(name) + sizeof(DWORD) + 1;
  234.  
  235.     FileTimeToDosDateTime(&filetime, &dosdate, &dostime);
  236.     written = MAKELONG(dostime, dosdate);
  237.     ptree->buf = Realloc(ptree->buf, newlen);
  238.     memcpy(ptree->buf + ptree->len, &written, sizeof(DWORD));
  239.     strcpy(ptree->buf + ptree->len + sizeof(DWORD), name);
  240.     ptree->len = newlen;
  241.     ptree->count++;
  242. }
  243.  
  244. /* MakeArray() - Build file entry array from list of files
  245. */
  246.  
  247. static int MakeArray(TREE *ptree)
  248. {
  249.     int i;
  250.     LPTSTR buf = ptree->buf;
  251.  
  252.     ptree->finfo = Calloc(ptree->count+1, sizeof(FINFO));
  253.     for (i=0; i<ptree->count; i++)
  254.     {
  255.         memcpy(&ptree->finfo[i].written, buf, sizeof(DWORD));
  256.         buf += sizeof(DWORD);
  257.         ptree->finfo[i].name = buf;
  258.         buf += strlen(buf) + 1;
  259.     }
  260.     memset(&ptree->finfo[i].written, 0xff, sizeof(DWORD));
  261.     memset(highname, 0xff, sizeof(highname));
  262.     ptree->finfo[i].name = highname;
  263.     return ptree->count;
  264. }
  265.  
  266. /* MatchMask() - See if a file name matches a mask
  267. */
  268.  
  269. static BOOL MatchMask(LPCTSTR pname, LPCTSTR pmask)
  270. {
  271.     LPCSTR pn = pname, pm;
  272.  
  273.     if (pmask == NULL || pname == NULL)
  274.         return FALSE;
  275.     for (pm=pmask; *pn && *pm; pm=AnsiNext(pm))
  276.     {
  277.         switch (*pm)
  278.         {
  279.         case '?':
  280.             if (*pn)
  281.                 pn = AnsiNext(pn);
  282.             break;
  283.         
  284.         case '*':
  285.             while (*pn && *pn != pm[1])
  286.                 pn = AnsiNext(pn);
  287.             break;
  288.         
  289.         default:
  290.              if (UPCHAR(*pn) != UPCHAR(*pm))
  291.                 return FALSE;
  292.             if (IsDBCSLeadByte(*pn) && UPCHAR(pn[1]) != UPCHAR(pm[1]) )
  293.                 return FALSE;
  294.             pn = AnsiNext(pn);
  295.         }
  296.     }
  297.     return (*pm || *pn) ? FALSE : TRUE;
  298. }
  299.  
  300. /* MatchMasks() - See if a file name matches a list of masks
  301. */
  302.  
  303. static BOOL MatchMasks(LPCTSTR name, LPCTSTR *masks)
  304. {
  305.     for(; *masks; masks++)
  306.         if (MatchMask(name, *masks))
  307.             return TRUE;
  308.     return FALSE;
  309. }
  310.  
  311. /* ReadTree() - Read a directory tree
  312. */
  313.  
  314. static int ReadTree(LPCTSTR path, TREE *ptree)
  315. {
  316.     TCHAR wild[_MAX_PATH];
  317.     TCHAR fullname[_MAX_PATH];
  318.     WIN32_FIND_DATA fdata;
  319.     HANDLE hfind;
  320.     LPTSTR name = fdata.cFileName;
  321.  
  322.     Trace(++trace_level, "%s", path);
  323.     FullSpec(wild, path, "*.*");
  324.     if ( (hfind = FindFirstFile(wild, &fdata)) != INVALID_HANDLE_VALUE)
  325.     {
  326.         do {
  327.             if (ISDOTDIR(name) || name[0] == 0)
  328.                 continue;
  329.             if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  330.             {
  331.                 if (!MatchMasks(name, ptree->skipdirs))
  332.                     ReadTree(FullSpec(fullname, path, name), ptree);
  333.             } else if (MatchMasks(name, ptree->includes) && !MatchMasks(name, ptree->excludes))
  334.                 AddToList(ptree, FullSpec(fullname, path, name), fdata.ftLastWriteTime);
  335.         } while (FindNextFile(hfind, &fdata));
  336.         FindClose(hfind);
  337.     }
  338.     trace_level--;
  339.     return ptree->count;
  340. }
  341.  
  342. /* ReportCopyError() - Report error from copying a file
  343. */
  344.  
  345. static BOOL ReportCopyError(LPCTSTR from, LPCTSTR toname)
  346. {
  347.     TCHAR buf[1000];
  348.     DWORD err = GetLastError();
  349.     
  350.     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
  351.         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL);
  352.     return Warning(ERR_CANTCOPY, from, toname, buf);
  353. }
  354.  
  355. /* MakeDirIfNeeded() - Create a directory if needed (and parent directory(ies))
  356. */
  357.  
  358. static BOOL MakeDirIfNeeded(char *newdir)
  359. {
  360.     TCHAR parentdir[_MAX_PATH];
  361.     DWORD attr, err;
  362.     
  363.     attr = GetFileAttributes(newdir);
  364.     if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY))
  365.         return TRUE;                            /* Directory already exists */
  366.     if (attr != 0xffffffff)    
  367.         return Warning(ERR_DIRFILE, newdir);    /* File exists with same name */
  368.     err = GetLastError() & 0x7fff;
  369.     if (err != ERROR_PATH_NOT_FOUND && err != ERROR_FILE_NOT_FOUND)
  370.         return Warning(ERR_DIRACCESS, newdir);    /* Cannot access directory */
  371.     if (!CreateDirectory(newdir, NULL))
  372.     {
  373.         err = GetLastError();
  374.  
  375.         DirName(newdir, parentdir);
  376.         if (!parentdir[0])
  377.             Abort(ERR_PATHSPEC, newdir);        /* Something way wrong */
  378.         if (!MakeDirIfNeeded(parentdir))
  379.             return FALSE;
  380.         if (!CreateDirectory(newdir, NULL))
  381.             return Warning(ERR_MAKEDIR, newdir);
  382.     }
  383.     return TRUE;
  384. }
  385.  
  386. /* Copy() - Copy a file, with trace and count
  387. */
  388.  
  389. static BOOL Copy(LPCTSTR from, LPCTSTR toname, int which)
  390. {
  391.     LPCTSTR what = testmode ? "Would copy" : "Copying";
  392.     TCHAR dirname[MAX_PATH];
  393.  
  394.     Trace(1, "%s %s", what, toname);
  395.     if (!testmode)
  396.     {
  397.         if (!MakeDirIfNeeded(DirName(toname, dirname)))
  398.             return FALSE;
  399.         if (!CopyFile(from, toname, FALSE))
  400.             return ReportCopyError(from, toname);
  401.     }
  402.     newcopied[which]++;
  403.     return TRUE;
  404. }
  405.  
  406. /* Update() - Update a file, with trace and count
  407. */
  408.  
  409. static BOOL Update(LPCTSTR from, LPCTSTR toname, int which)
  410. {
  411.     LPCTSTR what = testmode ? "Would update" : "Updating";
  412.     Trace(1, "%s %s", what, toname);
  413.     if (!testmode)
  414.         if (!CopyFile(from, toname, FALSE))
  415.             ReportCopyError(from, toname);
  416.     updated[which]++;
  417.     return TRUE;
  418. }
  419.  
  420. /* ProcessLists() - Compare the two file trees and perform
  421. ** appropriate operations
  422. */
  423.  
  424. static void ProcessLists(TREE *ptree1, TREE *ptree2)
  425. {
  426.     int pos1 = 0, pos2 = 0, c;
  427.     int lx1 = strlen(ptree1->dir), lx2 = strlen(ptree2->dir);
  428.     LPTSTR name1, name2;
  429.     DWORD time1, time2;
  430.     TCHAR tofile[_MAX_PATH];
  431.  
  432.     while (pos1 < ptree1->count || pos2 < ptree2->count)
  433.     {
  434.         name1 = ptree1->finfo[pos1].name + lx1;    /* Names without base dir */
  435.         name2 = ptree2->finfo[pos2].name + lx2;
  436.         time1 = ptree1->finfo[pos1].written;
  437.         time2 = ptree2->finfo[pos2].written;
  438.  
  439.         c = stricmp(name1, name2);
  440.         if (c < 0)            /* Catch up tree1 */
  441.             Copy(ptree1->finfo[pos1++].name, FullSpec(tofile, ptree2->dir, name1), 1);
  442.         else if (c > 0)        /* Catch up tree2 */
  443.             Copy(ptree2->finfo[pos2++].name, FullSpec(tofile, ptree1->dir, name2), 2);
  444.         else {
  445.             if (time1 < time2)
  446.                 Update(ptree2->finfo[pos2].name, FullSpec(tofile, ptree1->dir, name2), 2);
  447.             else if (time1 > time2)
  448.                 Update(ptree1->finfo[pos1].name, FullSpec(tofile, ptree2->dir, name1), 1);
  449.             else {
  450.                 Trace(2, "Skipping same %s", ptree2->finfo[pos2].name + lx2);
  451.                 skipped++;
  452.             }
  453.             pos1++;
  454.             pos2++;
  455.         }
  456.     }
  457. }
  458.  
  459. /* PrintSummary() - Output a summary of the operations performed
  460. */
  461.  
  462. static void PrintSummary(void)
  463. {
  464. #define PLURAL(n) (n),((n==1)?"":"s")
  465.     fprintf(stdout, "%7d new file%s copied from %s to %s\n", PLURAL(newcopied[1]),
  466.         directory1, directory2);
  467.     fprintf(stdout, "%7d new file%s copied from %s to %s\n", PLURAL(newcopied[2]),
  468.         directory2, directory1);
  469.     fprintf(stdout, "%7d file%s updated from %s to %s\n", PLURAL(updated[1]),
  470.         directory1, directory2);
  471.     fprintf(stdout, "%7d file%s updated from %s to %s\n", PLURAL(updated[2]),
  472.         directory2, directory1);
  473.     fprintf(stdout, "%7d file%s were identical and skipped\n", PLURAL(skipped));
  474. }
  475.  
  476. /* SortName() - Sort comparison routines (by file name, not case-sensitive)
  477. */
  478.  
  479. static int SortName(const void *pe1, const void *pe2)
  480. {
  481.     const FINFO *pc1 = (FINFO *)pe1;
  482.     const FINFO *pc2 = (FINFO *)pe2;
  483.     return stricmp(pc1->name, pc2->name);
  484. }
  485.  
  486. /* SplitMasks() - Split a ;-separated list into an array of pointers
  487. */
  488.  
  489. static int SplitMasks(LPTSTR masks, LPTSTR **parray)
  490. {
  491.     LPTSTR pm;
  492.     LPTSTR *pa = NULL;
  493.     int c = 0;
  494.  
  495.     for (pm = strtok(masks, ";"); pm; pm = strtok(NULL, ";"))
  496.     {
  497.         pa = Realloc(pa, sizeof(LPTSTR) * (c+1));
  498.         pa[c++] = pm;
  499.     }
  500.     pa = Realloc(pa, sizeof(LPTSTR) * (c+1));        /* Terminating entry */
  501.     pa[c] = NULL;
  502.     *parray = pa;
  503.     return c; 
  504. }
  505.  
  506. /* GetCmdFile() - Read the command file and set variables
  507. */
  508.  
  509. static void GetCmdFile(LPTSTR cmdfile)
  510. {
  511.     FILE *fp;
  512.     TCHAR linein[MAX_PATH * 2];
  513.     LPTSTR parg;
  514.  
  515.     if ( (fp = fopen(cmdfile, "r")) == NULL)
  516.         Abort(ERR_OPENCMD, cmdfile);
  517.     while (fgets(linein, sizeof(linein), fp))
  518.     {
  519.         if (linein[0] == '!' || linein[0] == ';' || linein[0] == '/')
  520.             continue;
  521.         if ( (parg = strchr(linein, ':')) == NULL)
  522.             continue;
  523.         linein[strlen(linein)-1] = 0;        /* Strip NL */
  524.         *parg++ = 0;
  525.         while (*parg == ' ' || *parg == '\t')
  526.             parg++;
  527.         if (!stricmp(linein, "dir1"))
  528.             strcpy(directory1, parg);
  529.         else if (!stricmp(linein, "dir2"))
  530.             strcpy(directory2, parg);
  531.         else if (!stricmp(linein, "include"))
  532.             strcpy(includes, parg);
  533.         else if (!stricmp(linein, "exclude"))
  534.             strcpy(excludes, parg);
  535.         else if (!stricmp(linein, "skipdir"))
  536.             strcpy(skipdirs, parg);
  537.     }
  538.     if (directory1[0] == 0 || directory2[0] == 0)
  539.         Abort(ERR_NODIR);
  540.     fclose(fp);
  541. }
  542.  
  543. /* GetOptions() - Get command line options
  544. **    (where oh where is getopt when you want it?)
  545. */
  546.  
  547. static void GetOptions(int argc, char *argv[])
  548. {
  549.     int i;
  550.  
  551.     for (i=1; i<argc && argv[i] && argv[i][0] == '-'; argc--, argv++)
  552.     {
  553.         switch (argv[i][1])
  554.         {
  555.         case 't':        trace = argv[i][2]-'0';        break;
  556.         case 'q':        testmode = TRUE;            break;
  557.         default:        Abort(ERR_USAGE);
  558.         }
  559.     }
  560.     for (; i<argc; i++)
  561.         strcat(strcat(cmdfile, argv[i]), " ");
  562.     if (strlen(cmdfile) > 1)
  563.         cmdfile[strlen(cmdfile)-1] = 0;
  564.     else
  565.         Abort(ERR_NOCMD);
  566. }
  567.  
  568. /* main()
  569. */
  570.  
  571. int main(int argc, char *argv[])
  572. {
  573.     GetOptions(argc, argv);
  574.     GetCmdFile(cmdfile);
  575.     SplitMasks(includes, &tree1.includes);
  576.     SplitMasks(excludes, &tree1.excludes);
  577.     SplitMasks(skipdirs, &tree1.skipdirs);
  578.     tree2.includes = tree1.includes;
  579.     tree2.excludes = tree1.excludes;
  580.     tree2.skipdirs = tree1.skipdirs;
  581.     Trace(1, "Reading %s...", directory1);
  582.     strcpy(tree1.dir, directory1);
  583.     ReadTree(directory1, &tree1);
  584.     MakeArray(&tree1);
  585.     Trace(1, "Reading %s...", directory2);
  586.     strcpy(tree2.dir, directory2);
  587.     ReadTree(directory2, &tree2);
  588.     MakeArray(&tree2);
  589.     if (tree1.count == 0 && tree2.count == 0)
  590.         Abort(ERR_NOTHING, directory1, directory2);
  591.     Trace(1, "Sorting %s list...", directory1);
  592.     qsort(tree1.finfo, tree1.count, sizeof(FINFO), SortName);
  593.     Trace(1, "Sorting %s list...", directory2);
  594.     qsort(tree2.finfo, tree2.count, sizeof(FINFO), SortName);
  595.     Trace(1, "Processing lists");
  596.     ProcessLists(&tree1, &tree2);
  597.     PrintSummary();
  598.     return 0;
  599. }
  600.